/*
 * Copyright by LunaSec (owned by Refinery Labs, Inc)
 *
 * Licensed under the Business Source License v1.1
 * (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * https://github.com/lunasec-io/lunasec/blob/master/licenses/BSL-LunaTrace.txt
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { Cvss_Environmental_Adjustment } from '../../../hasura-api/generated';
import { CvssAdjustments, FolderSettings } from '../types';

const abbreviationMap: Record<string, string> = {
  attack_vector: 'AV',
  attack_complexity: 'AC',
  privileges_required: 'PR',
  user_interaction: 'UI',
  scope: 'S',
  confidentiality_impact: 'C',
  integrity_impact: 'I',
  availability_impact: 'A',
  confidentiality_requirement: 'CR',
  integrity_requirement: 'IR',
  availability_requirement: 'AR',
};

// Maps from each possible abbreviation to the enum that is used to rank its value
// Note that some values are in inverse order to what you might expect. This is because we will always take the highest value when combining.
// // for things that override the base vulnerability, we only want to lower the score, never raise it.
// // For instance, we may lower the attack vector to network adjascent if someone tells us their environment is network adjascent, but raising a local only vuln to adjascent in that case wouldn't make sense
// // For things that modify instead of override, such as impact modifiers and temporal modifiers, we want to take the highest.
const scoreOrders: Record<string, string[]> = {
  AV: ['X', 'N', 'A', 'L', 'P'],
  AC: ['X', 'H', 'L'],
  PR: ['X', 'H', 'L', 'N'],
  UI: ['X', 'R', 'N'],
  S: ['X', 'C', 'U'],
  C: ['X', 'H', 'L', 'N'],
  I: ['X', 'H', 'L', 'N'],
  A: ['X', 'H', 'L', 'N'],
  CR: ['X', 'L', 'M', 'H'],
  IR: ['X', 'L', 'M', 'H'],
  AR: ['X', 'L', 'M', 'H'],
  E: ['X', 'U', 'P', 'F', 'H'],
  RL: ['X', 'O', 'T', 'W', 'U'],
  RC: ['X', 'U', 'R', 'C'],
};

// assumes valid vectors
function parseVector(vectorString: string) {
  const vectorObject: Record<string, string> = {};
  vectorString.split('/').map((propString) => {
    const [propKey, propVal] = propString.split(':');
    vectorObject[propKey] = propVal;
  });
  return vectorObject;
}

// merge a property, used when combining multiple (partial) vectors into one
export function mergeValue(
  keyName: string,
  valOne: string | undefined,
  valTwo: string | undefined
): string | undefined {
  if (!valOne) {
    return valTwo;
  }
  if (!valTwo) {
    return valOne;
  }
  const scoreOrder = scoreOrders[keyName];
  if (!scoreOrder) {
    throw new Error('Failed to look up cvss subscore key:' + keyName);
  }
  const sortedVals = [valOne, valTwo].sort((a, b) => {
    return scoreOrder.indexOf(valOne) - scoreOrder.indexOf(valTwo);
  });

  return sortedVals[0];
}

export function parseAdjustment(adjustmentFromDb: CvssAdjustments): Record<string, string> {
  const adjustmentObject: Record<string, string> = {};
  Object.keys(abbreviationMap).forEach((modifierName) => {
    if (!(modifierName in adjustmentFromDb)) {
      throw new Error('Database mismatch between cvss adjustment names and those in the parser: ' + modifierName);
    }
    const storedAdjustmentValue = adjustmentFromDb[modifierName as keyof CvssAdjustments];
    adjustmentObject[abbreviationMap[modifierName]] = storedAdjustmentValue;
  });
  return adjustmentObject;
}

export function modifyVectorString(existingVector: string, modifications: Record<string, string>): string {
  // pull the vector apart into an object
  const vectorObject = parseVector(existingVector);
  // go through each modifier key that is coming in, merge it into the object
  Object.keys(modifications).map((modifierName) => {
    const mergedValue = mergeValue(modifierName, vectorObject[modifierName], modifications[modifierName]);
    if (mergedValue) {
      vectorObject[modifierName] = mergedValue;
    }
  });

  // rebuild the vector string
  const vectorSubStrings = Object.keys(vectorObject).map((vectorKey) => `${vectorKey}:${vectorObject[vectorKey]}`);
  return vectorSubStrings.join('/');
}

// Example database values and an example vector for quick reference
// CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
/*

    attack_vector               cvss_vector          NOT NULL DEFAULT 'X',
    attack_complexity           cvss_complexity      NOT NULL DEFAULT 'X',
    privileges_required         cvss_none_low_high   NOT NULL DEFAULT 'X',
    user_interaction            cvss_interaction     NOT NULL DEFAULT 'X',
    scope                       cvss_scope           NOT NULL DEFAULT 'X',
    -- impact metrics
    confidentiality_impact      cvss_none_low_high   NOT NULL DEFAULT 'X',
    integrity_impact            cvss_none_low_high   NOT NULL DEFAULT 'X',
    availability_impact         cvss_none_low_high   NOT NULL DEFAULT 'X',
    -- subscore modifiers
    confidentiality_requirement cvss_low_medium_high NOT NULL DEFAULT 'X',
    integrity_requirement       cvss_low_medium_high NOT NULL DEFAULT 'X',
    availability_requirement    cvss_low_medium_high NOT NULL DEFAULT 'X'
 */
